package cn.isjinhao.se.sql.grammar.statement.retrieve;

import cn.isjinhao.se.sql.function.evaluator.FunctionEvaluator;
import cn.isjinhao.se.sql.grammar.CurdStatement;
import cn.isjinhao.se.sql.grammar.GrammarErrorReporterDelegate;
import cn.isjinhao.se.sql.grammar.statement.*;
import cn.isjinhao.se.sql.grammar.statement.retrieve.represention.*;
import cn.isjinhao.se.sql.grammar.statement.retrieve.represention.OrderBySeg.OrderItem;
import cn.isjinhao.se.sql.grammar.statement.retrieve.visitor.SingleSelectMetaInfoDetector;
import cn.isjinhao.se.sql.lexical.scan.TokenSequence;
import cn.isjinhao.se.sql.lexical.token.Token;
import cn.isjinhao.se.sql.lexical.token.TokenType;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author 01395265
 * @date 2021/3/2
 */
public class SelectParser extends StatementParser {

    public SelectParser(TokenSequence tokenSequence, FunctionEvaluator functionEvaluator) {
        super(tokenSequence, functionEvaluator);
    }

    /**
     * select               ->  singleSelect (("union" | "minus" | "intersect" | "except") singleSelect)*
     * singleSelect         ->  columnSeg tableSeg (whereSeg)? (groupBySeg)? (orderBySeg)? (limitSeg)?
     * <p>
     * columnSeg            ->  "select" ("distinct" | "first" | "last")? columnRep ("," columnRep)*
     * columnRep            ->  (* | binaryArithmetic) ("as" IDENTIFIER)?
     * <p>
     * tableSeg             ->  "from" tableRep (("left" | "right" | "full" | "cross")? ("inner" | "outer")? ("join" | ",") tableRep ("on" logic)?)*
     * tableRep             ->  ("(" singleSelect ")" | IDENTIFIER)? IDENTIFIER
     * <p>
     * retrieveWhereSeg     ->  "where" logic
     * <p>
     * groupBySeg           ->  "group" "by" IDENTIFIER ("," IDENTIFIER)* ("having" logic)?
     * <p>
     * orderBySeg           ->  "order" "by" IDENTIFIER ("desc" | "asc") ("," IDENTIFIER ("desc" | "asc"))*
     * <p>
     * limitSeg             ->  "limit" number ("," number)?
     * <p>
     * logic                ->  condition (("or" | "and") condition)*
     * condition            ->  inCondition | existsCondition | comparison
     * inCondition          ->  IDENTIFIER ("not")? "in" "(" (single | (primary (, primary)*) ")"
     * existsCondition      ->  ("not")? "exists" "(" singleSelect ")"
     * comparison           ->  binaryArithmetic ((">" | "<" | ">=" | "<=" | "!=" | "=" | "like" | "contains") binaryArithmetic)?
     * binaryArithmetic     ->  unaryArithmetic (("+" | "-" | "*" | "/") unaryArithmetic)*
     * unaryArithmetic      ->  ("!"|"-") unaryArithmetic | primary
     * primary              ->  #{xxx} | ? | "true" | "false" | "null" | NUMBER | STRING | IDENTIFIER | function | "(" singleSelect ")" | "(" logic ")" | groupFunction
     * function             ->  functionName "(" binaryArithmetic? ("," binaryArithmetic)* ")"
     * functionName         ->  "date_to_timestamp" | "date_to_str" | "str_to_date" | "str_to_timestamp" | "timestamp_to_date" | "timestamp_to_str" | "now"
     * groupFunction        ->  groupFunctionName "(" IDENTIFIER ")"
     * groupFunctionName    ->  "avg" | "max" | "min" | "count" | "sum" | "flat"
     */
    @Override
    public CurdStatement parse() {
        CurdStatement select = select();
        saveSingleSelectContext(select, select, SingleSelectType.TOP);
        consume(TokenType.EOF, GrammarErrorReporterDelegate.SELECT_NOT_END_PARSE);
        select.accept(new SingleSelectMetaInfoDetector(this));
        return select;
    }


    /**
     * singleSelect (("union" | "minus" | "intersect" | "except") singleSelect)*
     */
    protected CurdStatement select() {
        int flag = 0;
        CurdStatement left = singleSelect();
        while (tokenSequence.curEqual(TokenType.CUNION, TokenType.CMINUS, TokenType.CEXCEPT, TokenType.CINTERSECT)) {
            Token operator = tokenSequence.takeCur();
            tokenSequence.advance();
            CurdStatement right = singleSelect();
            left = new Select(left, operator, right);
            flag++;
        }
        if (flag == 0) {
            left = new Select(left);
        }
        return left;
    }


    /**
     * columnSeg tableSeg (whereSeg)? (groupBySeg)? (orderBySeg)? (limitSeg)?
     */
    private CurdStatement singleSelect() {
        CurdStatement columnSeg = columnSeg();
        CurdStatement tableSeg = tableSeg();
        if (tableSeg == null) {
            return new SingleSelect(columnSeg, tableSeg, null, null, null, null);
        }
        CurdStatement whereSeg = null;
        if (tokenSequence.curEqual(TokenType.WHERE)) {
            whereSeg = whereSeg();
        }
        CurdStatement groupBySeg = null;
        if (tokenSequence.curEqual(TokenType.GROUP)) {
            groupBySeg = groupBySeg();
        }
        CurdStatement orderBySeg = null;
        if (tokenSequence.curEqual(TokenType.ORDER)) {
            orderBySeg = orderBySeg();
        }
        CurdStatement limitSeg = null;
        if (tokenSequence.curEqual(TokenType.LIMIT)) {
            limitSeg = limitSeg();
        }
        return new SingleSelect(columnSeg, tableSeg, whereSeg, groupBySeg, orderBySeg, limitSeg);
    }


    /**
     * "select" ("distinct" | "first" | "last")? columnRep ("," columnRep)*
     */
    private CurdStatement columnSeg() {
        if (!tokenSequence.curEqual(TokenType.SELECT)) {
            error(GrammarErrorReporterDelegate.SELECT_columnSeg_PARSE);
        }
        tokenSequence.advance();
        Token restriction = null;
        if (tokenSequence.curEqual(TokenType.DISTINCT, TokenType.FIRST, TokenType.LAST)) {
            restriction = tokenSequence.takeCur();
            tokenSequence.advance();
        }
        List<CurdStatement> columnRepList = new ArrayList<>();
        do {
            columnRepList.add(columnRep());
        } while ((tokenSequence.equalThenAdvance(TokenType.COMMA)));
        if (columnRepList.isEmpty()) {
            error(GrammarErrorReporterDelegate.SELECT_columnSeg_PARSE);
        }
        return new ColumnSeg(restriction, columnRepList);
    }

    /**
     * (* | binaryArithmetic) ("as" IDENTIFIER)?
     */
    private CurdStatement columnRep() {
        boolean starFlag = false;
        CurdStatement column;
        if (tokenSequence.curEqual(TokenType.STAR)) {
            starFlag = true;
            column = new Identifier(tokenSequence.takeCur());
            tokenSequence.advance();
        } else {
            Token token = tokenSequence.takeCur();
            if (TokenType.CASE.equals(token.getType())) {
                column = caseWhen();
            } else {
                column = binaryArithmetic();
            }
        }
        if (tokenSequence.equalThenAdvance(TokenType.AS)) {
            if (starFlag) {
                error(GrammarErrorReporterDelegate.SELECT_columnRep_PARSE);
            }
            Token token = tokenSequence.takeCur();
            tokenSequence.advance();
            return new ColumnRep(column, token);
        }
        return new ColumnRep(column);
    }

    private CurdStatement caseWhen() {
        consume(TokenType.CASE, GrammarErrorReporterDelegate.SELECT_caseWhen_PARSE);
        CurdStatement value = binaryArithmetic();
        List<CurdStatement> conditionList = new ArrayList<>();
        List<CurdStatement> resultList = new ArrayList<>();

        while (tokenSequence.equalThenAdvance(TokenType.WHEN)) {
            conditionList.add(binaryArithmetic());
            consume(TokenType.THEN, GrammarErrorReporterDelegate.SELECT_caseWhen_PARSE);
            resultList.add(binaryArithmetic());
        }
        Token token = tokenSequence.takeCur();
        CurdStatement defaultResult = null;
        if (TokenType.ELSE.equals(token.getType())) {
            tokenSequence.advance();
            defaultResult = binaryArithmetic();
        }

        consume(TokenType.END, GrammarErrorReporterDelegate.SELECT_caseWhen_PARSE);
        return new CaseWhen(value, conditionList, resultList, defaultResult);
    }


    /**
     * "from" tableRep (("left" | "right" | "full" | "cross")? ("inner" | "outer")? ("join" | ",") tableRep ("on" logic)?)*
     */
    private CurdStatement tableSeg() {
        if (!tokenSequence.curEqual(TokenType.FROM)) {
            return null;
        }
        tokenSequence.advance();
        CurdStatement left = tableRep();

        while (tokenSequence.curEqual(TokenType.JOIN, TokenType.COMMA, TokenType.LEFT, TokenType.RIGHT,
                TokenType.OUTER, TokenType.FULL, TokenType.INNER, TokenType.CROSS)) {

            Token qualifier = null;
            Token modifier = null;
            Token join = null;

            if (tokenSequence.curEqual(TokenType.LEFT, TokenType.RIGHT, TokenType.CROSS, TokenType.FULL)) {
                qualifier = tokenSequence.takeCur();
                tokenSequence.advance();
            }

            if (tokenSequence.curEqual(TokenType.INNER, TokenType.OUTER)) {
                modifier = tokenSequence.takeCur();
                if (qualifier != null && modifier.getType().equals(TokenType.INNER)) {
                    error(GrammarErrorReporterDelegate.SELECT_tableSeg_join_CHECK);
                }
                if (qualifier != null && qualifier.getType().equals(TokenType.CROSS)) {
                    error(GrammarErrorReporterDelegate.SELECT_tableSeg_join_CHECK);
                }
                tokenSequence.advance();
            }

            if (tokenSequence.curEqual(TokenType.COMMA, TokenType.JOIN)) {
                join = tokenSequence.takeCur();

                if ((qualifier != null || modifier != null) && join.getType().equals(TokenType.COMMA)) {
                    error(GrammarErrorReporterDelegate.SELECT_tableSeg_join_CHECK);
                }
                tokenSequence.advance();
            }

            CurdStatement right = tableRep();
            if (tokenSequence.equalThenAdvance(TokenType.ON)) {
                CurdStatement logic = logic();
                if (!checkLogicOnlyContainsComparison(logic)) {
                    error(GrammarErrorReporterDelegate.SELECT_tableSeg_logic_CHECK);
                }
                left = new TableSeg(qualifier, modifier, left, join, right, logic);
            } else {
                left = new TableSeg(qualifier, modifier, left, join, right);
            }
        }

        return left;
    }


    /**
     * 检查select语法是不是仅包含 comparison 语法。
     */
    private boolean checkLogicOnlyContainsComparison(CurdStatement curdStatement) {
        if (curdStatement == null) {
            return true;
        }
        if (curdStatement instanceof Grouping) {
            Grouping grouping = (Grouping) curdStatement;
            curdStatement = grouping.getSelectStatement();
        }
        if (curdStatement instanceof Logic) {
            Logic logic = (Logic) curdStatement;
            return checkLogicOnlyContainsComparison(logic.getLeftSelectStatement()) &&
                    checkLogicOnlyContainsComparison(logic.getRightSelectStatement());
        }
        return !(curdStatement instanceof ExistsCondition) && !(curdStatement instanceof InCondition) &&
                !(curdStatement instanceof SingleSelect) && !(curdStatement instanceof Select);
    }


    /**
     * ("(" singleSelect ")" | IDENTIFIER)? ("as")? IDENTIFIER
     */
    private CurdStatement tableRep() {
        boolean flag = false;
        CurdStatement curdStatement = null;
        if (checkSingleSelectValue(tokenSequence)) {
            consume(TokenType.LEFT_PAREN, GrammarErrorReporterDelegate.SELECT_tableRep_PARSE);
            curdStatement = singleSelect();
            consume(TokenType.RIGHT_PAREN, GrammarErrorReporterDelegate.SELECT_tableRep_PARSE);
            flag = true;
        }

        Token as = null;
        if (tokenSequence.curEqual(TokenType.AS)) {
            as = tokenSequence.takeCur();
            tokenSequence.advance();
        }

        if (flag && !tokenSequence.curEqual(TokenType.IDENTIFIER)) {
            error(GrammarErrorReporterDelegate.SELECT_tableRep_PARSE);
        }
        Token token = tokenSequence.takeCur();
        tokenSequence.advance();
        if (flag) {
            // 前者是真实表，后者是别名
            TableRep tableRep = new TableRep(curdStatement, token, as);
            // 存select语句的上下文。
            saveSingleSelectContext(curdStatement, tableRep, SingleSelectType.TABLE);
            return tableRep;
        }
        if (tokenSequence.curEqual(TokenType.IDENTIFIER)) {
            // 在有两个 IDENTIFIER 的时候，前者是真实表，后者是别名
            TableRep tableRep = new TableRep(new Identifier(token), tokenSequence.takeCur(), as);
            tokenSequence.advance();
            return tableRep;
        }
        // 只有一个 IDENTIFIER 的时候，是真实表
        return new TableRep(new Identifier(token));
    }


    /**
     * "group" "by" IDENTIFIER ("," IDENTIFIER)* ("having" logic)?
     */
    private CurdStatement groupBySeg() {
        consume(TokenType.GROUP, GrammarErrorReporterDelegate.SELECT_groupBySeg_PARSE);
        consume(TokenType.BY, GrammarErrorReporterDelegate.SELECT_groupBySeg_PARSE);
        List<Token> columnList = new ArrayList<>();
        do {
            columnList.add(tokenSequence.takeCur());
            tokenSequence.advance();
        } while (tokenSequence.equalThenAdvance(TokenType.COMMA));
        if (tokenSequence.equalThenAdvance(TokenType.HAVING)) {
            CurdStatement logic = logic();
            if (!checkLogicOnlyContainsComparison(logic)) {
                error(GrammarErrorReporterDelegate.SELECT_groupBySeg_PARSE);
            }
            return new GroupBySeg(columnList, logic);
        }
        return new GroupBySeg(columnList);
    }


    /**
     * "order" "by" IDENTIFIER ("desc" | "asc") ("," IDENTIFIER ("desc" | "asc"))*
     */
    private CurdStatement orderBySeg() {
        consume(TokenType.ORDER, GrammarErrorReporterDelegate.SELECT_orderBySeg_PARSE);
        consume(TokenType.BY, GrammarErrorReporterDelegate.SELECT_orderBySeg_PARSE);
        List<OrderBySeg.OrderItem> columnList = new ArrayList<>();
        do {
            Token token = tokenSequence.takeCur();
            tokenSequence.advance();
            if (tokenSequence.curEqual(TokenType.ASC, TokenType.DESC)) {
                columnList.add(new OrderItem(token, tokenSequence.takeCur()));
                tokenSequence.advance();
            } else {
                columnList.add(new OrderItem(token));
            }
        } while (tokenSequence.equalThenAdvance(TokenType.COMMA));
        return new OrderBySeg(columnList);
    }


    /**
     * "limit" number ("," number)?
     */
    private CurdStatement limitSeg() {
        consume(TokenType.LIMIT, GrammarErrorReporterDelegate.SELECT_limitSeg_PARSE);
        if (!tokenSequence.curEqual(TokenType.NUMBER)) {
            error(GrammarErrorReporterDelegate.SELECT_limitSeg_PARSE);
        }
        Token token = tokenSequence.takeCur();
        tokenSequence.advance();
        if (tokenSequence.equalThenAdvance(TokenType.COMMA)) {
            if (!tokenSequence.curEqual(TokenType.NUMBER)) {
                error(GrammarErrorReporterDelegate.SELECT_limitSeg_PARSE);
            }
            LimitSeg limitSeg = new LimitSeg(token, tokenSequence.takeCur());
            tokenSequence.advance();
            return limitSeg;
        }
        return new LimitSeg(token);
    }


    /**
     * inCondition | existsCondition | comparison
     */
    protected CurdStatement condition() {
        if (tokenSequence.curEqual(TokenType.EXISTS, TokenType.NOT)) {
            return existsCondition();
        } else if (tokenSequence.nextEqual(TokenType.NOT, TokenType.IN)) {
            return inCondition();
        }
        return comparison();
    }


    /**
     * IDENTIFIER ("not")? "in" "(" (single | (primary (, primary)*) ")"
     */
    private CurdStatement inCondition() {
        Token identifier = tokenSequence.takeCur();
        tokenSequence.advance();
        Token in = tokenSequence.takeCur();
        if (tokenSequence.curEqual(TokenType.NOT)) {
            tokenSequence.advance();
        }
        tokenSequence.advance();

        if (checkSingleSelectValue(tokenSequence)) {
            consume(TokenType.LEFT_PAREN, GrammarErrorReporterDelegate.SELECT_inCondition_PARSE);
            CurdStatement curdStatement = select();
            consume(TokenType.RIGHT_PAREN, GrammarErrorReporterDelegate.SELECT_inCondition_PARSE);
            InCondition inCondition = new InCondition(in, identifier, curdStatement);
            saveSingleSelectContext(curdStatement, inCondition, SingleSelectType.IN);
            return inCondition;
        }
        consume(TokenType.LEFT_PAREN, GrammarErrorReporterDelegate.SELECT_inCondition_PARSE);
        List<CurdStatement> ranges = new ArrayList<>();
        do {
            CurdStatement primary = primary();
            if (primary instanceof Literal) {
                ranges.add(primary);
            } else {
                error(GrammarErrorReporterDelegate.SELECT_inCondition_PARSE);
            }
        } while (tokenSequence.equalThenAdvance(TokenType.COMMA));
        consume(TokenType.RIGHT_PAREN, GrammarErrorReporterDelegate.SELECT_inCondition_PARSE);
        return new InCondition(in, identifier, ranges);
    }


    /**
     * ("not")? "exists" "(" singleSelect ")"
     */
    private CurdStatement existsCondition() {
        Token exists = tokenSequence.takeCur();
        if (tokenSequence.curEqual(TokenType.NOT)) {
            tokenSequence.advance();
        }
        tokenSequence.advance();
        consume(TokenType.LEFT_PAREN, GrammarErrorReporterDelegate.SELECT_existsCondition_PARSE);
        CurdStatement curdStatement = singleSelect();
        consume(TokenType.RIGHT_PAREN, GrammarErrorReporterDelegate.SELECT_existsCondition_PARSE);
        ExistsCondition existsCondition = new ExistsCondition(exists, curdStatement);
        // 存select语句的山下文。
        saveSingleSelectContext(curdStatement, existsCondition, SingleSelectType.EXISTS);
        return existsCondition;
    }


    /**
     * binaryArithmetic ((">" | "<" | ">=" | "<=" | "!=" | "=" | "like" | "contains") binaryArithmetic)?
     */
    @Override
    protected CurdStatement comparison() {
        CurdStatement curdStatement = super.comparison();
        if (curdStatement instanceof Comparison) {
            saveSingleSelectContext(curdStatement, curdStatement, SingleSelectType.PRIMARY);
        }
        return curdStatement;
    }


    /**
     * unaryArithmetic (("+" | "-" | "*" | "/") unaryArithmetic)*
     */
    @Override
    protected CurdStatement binaryArithmetic() {
        CurdStatement curdStatement = super.binaryArithmetic();
        if (curdStatement instanceof BinaryArithmetic) {
            saveSingleSelectContext(curdStatement, curdStatement, SingleSelectType.PRIMARY);
        }
        return curdStatement;
    }


    /**
     * ("!"|"-") unaryArithmetic | primary
     */
    @Override
    protected CurdStatement unaryArithmetic() {
        CurdStatement curdStatement = super.unaryArithmetic();
        if (curdStatement instanceof UnaryArithmetic) {
            saveSingleSelectContext(curdStatement, curdStatement, SingleSelectType.PRIMARY);
        }
        return curdStatement;
    }


    /**
     * #{xxx} | ? | "true" | "false" | "null" | NUMBER | STRING | IDENTIFIER | function | "(" singleSelect ")" | "(" logic ")" | groupFunction
     */
    @Override
    protected CurdStatement primary() {
        CurdStatement primary = super.primary();
        if (primary != null) {
            return primary;
        }

        Token token = tokenSequence.takeCur();
        if (checkGroupFunctionName(token)) {
            return groupFunction();
        }

        // singleSelect
        if (checkSingleSelectValue(tokenSequence)) {
            consume(TokenType.LEFT_PAREN, GrammarErrorReporterDelegate.SELECT_primary_PARSE);
            SingleSelect singleSelect = (SingleSelect) singleSelect();
            consume(TokenType.RIGHT_PAREN, GrammarErrorReporterDelegate.SELECT_primary_PARSE);
            return singleSelect;
        }

        // "(" logic ")"
        if (tokenSequence.equalThenAdvance(TokenType.LEFT_PAREN)) {
            CurdStatement curdStatement = logic();
            consume(TokenType.RIGHT_PAREN, GrammarErrorReporterDelegate.SELECT_primary_PARSE);
            return new Grouping(curdStatement);
        }
        error(GrammarErrorReporterDelegate.ALL_token_undefined_PARSE);
        return null;
    }


    /**
     * ("avg" | "max" | "min" | "count" | "sum", "flat") "(" IDENTIFIER ")"
     */
    protected CurdStatement groupFunction() {
        Token method = tokenSequence.takeCur();
        tokenSequence.advance();
        consume(TokenType.LEFT_PAREN, GrammarErrorReporterDelegate.SELECT_groupFunction_PARSE);
        Token identifier = tokenSequence.takeCur();
        tokenSequence.advance();
        consume(TokenType.RIGHT_PAREN, GrammarErrorReporterDelegate.SELECT_groupFunction_PARSE);
        return new GroupFunction(method, identifier);
    }

    protected static Set<TokenType> groupFunctionTypeSet = new HashSet<>();

    static {
        groupFunctionTypeSet.add(TokenType.MAX);
        groupFunctionTypeSet.add(TokenType.MIN);
        groupFunctionTypeSet.add(TokenType.FLAT);
        groupFunctionTypeSet.add(TokenType.COUNT);
        groupFunctionTypeSet.add(TokenType.AVG);
        groupFunctionTypeSet.add(TokenType.SUM);
    }

    protected boolean checkGroupFunctionName(Token token) {
        return groupFunctionTypeSet.contains(token.getType());
    }

    private boolean checkSingleSelectValue(TokenSequence tokenSequence) {
        Token cur = tokenSequence.takeCur();
        Token next = tokenSequence.takeNext();
        return TokenType.LEFT_PAREN.equals(cur.getType()) && TokenType.SELECT.equals(next.getType());
    }

}
